package org.gwt.grin.client.util;

import org.gwt.grin.client.ui.Scene;

import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Widget;

public class Graphics {

	private final AbsolutePanel target;
	private final Element targetElement;

	private int clientWidth, clientHeight;

	private int originX = 0;
	private int originY = 0;

	private int opacity = -1;

	private Rectangle clipRegion;

	public Graphics() {
		this(new Scene());
	}

	public Graphics(AbsolutePanel target) {
		assert target != null;
		this.target = target;
		this.targetElement = target.getElement();
	}

	public void resetClientSize() {
		clientWidth = targetElement.getClientWidth();
		clientHeight = targetElement.getClientHeight();
	}

	public int getClientWidth() {
		return clientWidth;
	}

	public int getClientHeight() {
		return clientHeight;
	}

	/**
	 * Translates the origin of the graphics context to the point (<i>x</i>,
	 * <i>y</i>) in the current coordinate system. All coordinates used in
	 * subsequent rendering operations on this graphics context will be relative
	 * to this new origin.
	 * 
	 * @param x
	 *            the <i>x</i> coordinate
	 * @param y
	 *            the <i>y</i> coordinate
	 */
	public void translate(int x, int y) {
		originX += x;
		originY += y;
	}

	public void setBounds(final Widget widget, final int x, final int y,
			final int w, final int h) {
		final Element elem = widget.getElement();
		final Style widgetStyle = elem.getStyle();

		widgetStyle.setWidth(w, Unit.PX);
		widgetStyle.setHeight(h, Unit.PX);

		if (clipRegion != null) {
			clipWidget(widget);
		}
		if (opacity >= 0 && opacity <= 255) {
			setOpacity(widget.getElement(), opacity);
		}
		if (!widget.isAttached()) {
			target.add(widget, originX + x, originY + y);
		} else {
			target.setWidgetPosition(widget, originX + x, originY + y);
		}
	}

	private void clipWidget(Widget w) {
		int x1 = w.getAbsoluteLeft() - target.getAbsoluteLeft();
		int y1 = w.getAbsoluteTop() - target.getAbsoluteTop();
		int x2 = x1 + w.getOffsetWidth();
		int y2 = y1 + w.getOffsetHeight();
		int X1 = clipRegion.x;
		int Y1 = clipRegion.y;
		int X2 = X1 + clipRegion.width;
		int Y2 = Y1 + clipRegion.height;

		x1 = (x1 < X1) ? (X1 - x1) : 0;
		y1 = (y1 < Y1) ? (Y1 - y1) : 0;

		x2 = (x2 > X2) ? w.getOffsetWidth() - (x2 - X2) : w.getOffsetWidth();
		y2 = (y2 > Y2) ? w.getOffsetHeight() - (y2 - Y2) : w.getOffsetHeight();

		setClip(w.getElement(), getRectString(y1, x2, y2, x1));
	}

	private void setClip(Element elem, String rect) {
		elem.getStyle().setProperty("clip", rect);
	}

	private String getRectString(int top, int right, int bottom, int left) {
		return "rect(" + top + "px, " + right + "px, " + bottom + "px, " + left
				+ "px)";
	}

	private void setOpacity(Element elem, int opacity) {
		Style style = elem.getStyle();
		style.setOpacity(opacity / 255.0);
		style.setProperty("filter", "alpha(opacity=" + opacity + ")");
	}

	/**
	 * Returns the bounding rectangle of the current clipping area.
	 * 
	 * @param lastClipRegion
	 */
	public void getClipBounds(Rectangle r) {
		if (clipRegion != null) {
			r.x = clipRegion.x;
			r.y = clipRegion.y;
			r.width = clipRegion.width;
			r.height = clipRegion.height;
		}
	}

	/**
	 * 
	 * @param r
	 */
	public void setClipBounds(Rectangle r) {
		this.clipRegion = (r == null) ? null : new Rectangle(originX + r.x,
				originY + r.y, r.width, r.height);
	}

	/**
	 * 
	 * @return
	 */
	public int getOpacity() {
		return opacity;
	}

	/**
	 * 
	 * @param opacity
	 */
	public void setOpacity(int opacity) {
		this.opacity = opacity;
	}

}
